﻿#include "Readline.h"
#include "Reader.h"
#include "Printer.h"
#include "Env.h"
#include "Util.h"
#include "StaticList.h"
#include "Exception.h"
#include "Validation.h"
#include "Core.h"

Lisp::ValPtr READ(const std::string &input);
Lisp::ValPtr EVAL(Lisp::ValPtr ast, Lisp::EnvPtr env);
std::string PRINT(Lisp::ValPtr val);
std::string Rep(const std::string &input, Lisp::EnvPtr env);

static Lisp::Readline s_readline;
static Lisp::EnvPtr s_repEnv(new Lisp::Env);

int main()
{
  Lisp::InitNativeBuiltIns(s_repEnv);

  const std::string prompt = "user> ";
  std::string input;
  while (s_readline.Get(prompt, input))
  {
    std::string out = "";
    try
    {
      out = Rep(input, s_repEnv);
    }
    catch (const Lisp::EmptyInputException &e)
    {
      continue;
    }
    catch (const Lisp::RuntimeException &e)
    {
      out = "Err: " + e.What();
    }
    std::cout << out << std::endl;
  }
  return 0;
}

Lisp::ValPtr READ(const std::string &input)
{
  return Lisp::ReadStr(input);
}

Lisp::ValPtr EVAL(Lisp::ValPtr ast, Lisp::EnvPtr env)
{
  if (!env)
    env = s_repEnv;

  const Lisp::ListType *list = DYNAMIC_CAST(Lisp::ListType, ast);
  if (!list || (list->Size() == 0))
  {
    return ast->Eval(env);
  }

  if (const Lisp::SymbolType *symbol = DYNAMIC_CAST(Lisp::SymbolType, list->At(0)))
  {
    const std::string special = symbol->Value();
    const size_t argCount = list->Size() - 1;

    if (symbol->Value() == "def!")
    {
      Lisp::CheckArgsIs("def!", argCount, 2);
      const Lisp::SymbolType *symbol = VALUE_CAST(Lisp::SymbolType, list->At(1));
      return env->Set(symbol->Value(), EVAL(list->At(2), env));
    }
    else if (symbol->Value() == "let*")
    {
      Lisp::CheckArgsIs("let*", argCount, 2);
      const Lisp::ListType *bindings = VALUE_CAST(Lisp::ListType, list->At(1));
      const size_t count = Lisp::CheckArgsEven("let*", bindings->Size());
      Lisp::EnvPtr newEnv(new Lisp::Env(env));
      for (size_t i = 0; i < count; i += 2)
      {
        const Lisp::SymbolType *symbol = VALUE_CAST(Lisp::SymbolType, bindings->At(i));
        newEnv->Set(symbol->Value(), EVAL(bindings->At(i + 1), newEnv));
      }
      return EVAL(list->At(2), newEnv);
    }
  }
  return ast->Eval(env);
}

Lisp::ValPtr APPLY(Lisp::ValPtr op, Lisp::ListPtr args)
{
  const Lisp::ApplicableType *handler = DYNAMIC_CAST(Lisp::ApplicableType, op);
  LISP_CHECK(handler != NULL,
             "\"%s\" is not applicable", op->ToString(true).c_str());

  return handler->Apply(args);
}

std::string PRINT(Lisp::ValPtr val)
{
  return Lisp::PrintStr(val);
}

std::string Rep(const std::string &input, Lisp::EnvPtr env)
{
  std::string res = PRINT(EVAL(READ(input), env));
  return res;
}

Lisp::ValPtr Readline(const std::string &prompt)
{
  std::string input;
  if (s_readline.Get(prompt, input))
    return Lisp::StringVal(input);
  return Lisp::NilVal();
}